# Create your views here.


##################
#  2011
#  NEW FILE VERSION FOR THREAD-SAFE, DB-CACHED, STATELESS FACETED MANAGER OBJECT 
#
##################

from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, redirect 
from django.template import RequestContext, Context, loader
from django.template.loader import select_template
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.core.cache import cache

from django.http import QueryDict

# from religions.models import *


from djfacet.constants import *
from djfacet.fb_utils.utils import *
from djfacet.fb_utils.template import render_block_to_string
from djfacet.load_all import *
from djfacet.models import CachedFacetedManager





def get_defaultResType():
	""" gets the specified default resulttype - or the first one in case we can't find it """
	for x in DJ_SPECS.result_types:
		if x.get('isdefault'):
			return x
	return DJ_SPECS.result_types[0]
			


def getHTMLColor(index):
	try:
		return SOFT_HTML_COLORS[index]
	except:
		return SOFT_HTML_COLORS[1]



def access_fmglobal():
	""" 
	Method for loading the Faceted Manager instance and keeping it in memory.
		-> if not loaded already (at server start time) it's loaded from the DB (using pickle)
			-> if the DB cache is disabled, it loads it in memory using the global variable
				-> mind that this last option is not thread safe, so it shouldn't be used in the production environment!!
				-> if using the DB, first issue: 'python manage.py fb_store_facetmanager'
	 """
	FM_GLOBAL = cache.get('DJ_FM_GLOBAL')

	if (not DJFACET_CACHE) and (not FM_GLOBAL):		
		djfacetlog("\n***** Init FM_GLOBAL : using no database [you can pre-initialize the Faceted Manager instance and store it in the DB via the command <fb_store_facetmanager> for faster performance]*****\n", True)

		loaded_facet_groups = []
		facets_for_template = []

		# load facet specs and init the Faceted Manager object
		# 1: create groups from SPECS.facet_groups	//	2: load facets into groups using SPECS.facetslist
		valid_groups = [x for x in reversed(sorted(DJ_SPECS.facet_groups, key=lambda (k): k['position'])) if x['default']]
		for n in range(len(valid_groups)):
			x = valid_groups[n]
			bkcolor = x.get('bkcolor', getHTMLColor(n))
			loaded_facet_groups.append(FacetsGroup(x['uniquename'], x['label'], x['position'], bkcolor=bkcolor)) 
		for g in loaded_facet_groups:
			g.buildfacets_fromspecs(DJ_SPECS.facetslist)

		RESULT_TYPES = DJ_SPECS.result_types 

		# initialize the faceted manager and add it to the django cache

		FM_GLOBAL = FacetedManager(loaded_facet_groups, RESULT_TYPES)
		cache.set('DJ_FM_GLOBAL', FM_GLOBAL, 90000000000000)


	if DJFACET_CACHE and not FM_GLOBAL:
		djfacetlog("\n***** Init FM_GLOBAL : using database-cached version and LOCK mechanism to make it thread-safe *****\n")

		from django.db import connection
		cursor = connection.cursor()
		try:
			cursor.execute('LOCK TABLES %s WRITE' % 'djfacet_cachedfacetedmanager')
			x = CachedFacetedManager.objects.all()[0]
			FM_GLOBAL = x.manager
		except:
			raise Exception("\n***** DJFACET : could not init the Faceted Manager object from Database: have you created it? Use the management command to create it, or set DJFACET_CACHE to False")
		finally:
			cursor.execute('UNLOCK TABLES')
		cache.set('DJ_FM_GLOBAL', FM_GLOBAL, 90000000000000)

	# finally:	
	return FM_GLOBAL
		




def get_facetsForTemplate(queryargs, activeIDs, resulttype, ajax_ok=True):
	""" helper function that outputs a nested list containing info on facet-groups, facets and values. This list 
	is then processed by the template.
		[(group, [(facet, [values..]), (facet, [values..])... ]), (another group...etc...)]
		
	ajax_ok=True e' un'ulteriore sicura per permettere di disattivare ajax sulla splash page!
	  """
	FM_GLOBAL = access_fmglobal()
	facetgroups_and_facets = []
	if DJFACET_AJAX and ajax_ok: 
		for group in FM_GLOBAL.facetsGroups: 
			# in this case we don't need to preload all facet contents, cause they'll be updated via ajax
			facetgroups_and_facets.append((group, [(facet, []) for facet in group.facets if facet.get_behaviour(resulttype)]))
	elif DJ_2COLUMNS_INNERFACET:
		for group in FM_GLOBAL.facetsGroups:
			facetgroups_and_facets.append((group, [(facet, split_list_into_two(FM_GLOBAL.refresh_facetvalues(queryargs, activeIDs, resulttype, facet))) for facet in group.facets if facet.get_behaviour(resulttype)]))
	else:
		for group in FM_GLOBAL.facetsGroups:
			facetgroups_and_facets.append((group, [(facet, FM_GLOBAL.refresh_facetvalues(queryargs, activeIDs, resulttype, facet)) for 
					facet in group.facets if facet.get_behaviour(resulttype)]))				
	return facetgroups_and_facets



def create_queryUrlStub(query_filtersUrl):
	""" from the active filters, pre-compose the url so that it can be used in the template more easily where is needed """
	stringa = "filter=0"  # initialize
	newurl_stub = ""
	if query_filtersUrl:
		q=QueryDict(stringa).copy()
		q.setlist('filter', query_filtersUrl)
		newurl_stub =q.urlencode()	# eg: 'filter=2&filter=3&filter=4&filter1=2&filter1=3&filter1=4'
		newurl_stub = "&"+ newurl_stub
		# print newurl_stub
	return newurl_stub




def home(request):
	"""
	Main dispatcher: checks if it is the first page (no filters and not item) of the FB or not, and redirects accordingly.
	The DJ_SPLASHPAGE constant defaults to True and indicates that the all-facets splash page is desired.
	"""
	query_filtersUrl = request.GET.getlist('filter')
	item = request.GET.get('item', None)
	if DJ_SPLASHPAGE and not query_filtersUrl and not item:
		template, context = splash_page(request)		
	else:
		template, context = search_page(request)		
	return render_to_response(	template, 
								context, 
								context_instance=RequestContext(request))




def splash_page(request):

	FM_GLOBAL = access_fmglobal()
	RESULT_TYPES = DJ_SPECS.result_types 
	DEFAULT_RESULTTYPE = get_defaultResType()

	# -------
	resulttype = request.GET.get( 'resulttype', None )	 # what result-types are selected (e.g., 'factoids')
	ordering = request.GET.get( 'ordering', 'default')	
	query_filtersUrl = [] 
	query_filtersBuffer = request.session.get('query_filtersBuffer', [])  # a list of fValues objects
	activeIDs = request.session.get('activeIDs', [])
	item = None	
	# -------

	if not resulttype or resulttype not in [x['uniquename'] for x in RESULT_TYPES]:
		resulttype = DEFAULT_RESULTTYPE['uniquename']

	# CREATE THE FACETS COLUMNS: 
	facetgroups_and_facets = get_facetsForTemplate([], activeIDs, resulttype)

	# HELPER URL STRINGA creation, from the active filters
	newurl_stub = create_queryUrlStub(query_filtersUrl)

	# RESET THE SESSION INFO 
	request.session['active_resulttype'] = resulttype	# a string (=uniquename) ... used by updateFacets
	request.session['query_filtersBuffer'] = []	# a list of FValues..
	request.session['activeIDs'] = activeIDs
	request.session.set_expiry(300) # 5 minutes ..	too much?


	context = {	  
		'user_is_logged_in' : request.user.is_authenticated(),
		'facetgroups_and_facets' :	 facetgroups_and_facets,			

		'ajaxflag' : DJFACET_AJAX,
		'twocolumnsflag' : DJ_2COLUMNS_INNERFACET,
		'splash_page' : DJ_SPLASHPAGE,

		'result_types' : RESULT_TYPES,	
		'result_typeObj' : FM_GLOBAL.get_resulttype_from_name(resulttype),	  
		'ordering' : ordering,

		'query_filtersBuffer' : [],
		'newurl_stub' : newurl_stub,
		}


	return ('djfacet/splash_page.html', context)






def search_page(request):

	FM_GLOBAL = access_fmglobal()
	RESULT_TYPES = DJ_SPECS.result_types 
	DEFAULT_RESULTTYPE = get_defaultResType()
	
	# -------
	try:
		page = int(request.GET.get('page', '1'))
	except ValueError:
		page = 1
	resulttype = request.GET.get( 'resulttype', None )	 # what result-types are selected (e.g., 'factoids')
	ordering = request.GET.get( 'ordering', 'default')	
	query_filtersUrl = request.GET.getlist('filter')	# getlist gets all parameters with same name!!!!
	query_filtersBuffer = request.session.get('query_filtersBuffer', [])  # a list of fValues objects
	activeIDs = request.session.get('activeIDs', [])
	item = request.GET.get('item', None)	
	# -------

	if not resulttype or resulttype not in [x['uniquename'] for x in RESULT_TYPES]:
		resulttype = DEFAULT_RESULTTYPE['uniquename']

	try:
		item = int(item)
		single_item = FM_GLOBAL.get_resulttype_from_name(resulttype)['infospace'].objects.get(pk=item)
	except:
		single_item = None
	single_item_fields = None


	# CHECK THAT THE IDS PASSED IN FILTERS_URL ARE EXISTING AND VALID FOR THIS RESULT TYPE,
	# OTHERWISE REDIRECT TO A NEW URL : this way we always maintain a one-to-one correspondance between url filters and args in query
	redirect_flag = False	
	query_filtersUrl_Clean = [] 
	for fvalue_id in query_filtersUrl:
		try:
			facetvalue = FM_GLOBAL.get_facetvalue_from_id(fvalue_id)
			if facetvalue.facet.get_behaviour(resulttype):
				query_filtersUrl_Clean.append(fvalue_id)
			else:
				redirect_flag = True
				djfacetlog("FacetViews>> The requested facetvalue [%s] doesn't have a behaviour for resulttype %s"	% (fvalue_id, resulttype), True)
		except Exception, e:
			redirect_flag = True
			djfacetlog("FacetViews>> Can't identify facetvalue from ID! Error: %s" % (e), True)
			continue 
	if redirect_flag:
		djfacetlog("FacetViews>> Redirecting; the url contained not valid facetvalues IDs!", True)
		newurl_stub = create_queryUrlStub(query_filtersUrl_Clean)
		newurl_stub = "%s?resulttype=%s%s" % (request.path, resulttype , newurl_stub)
		# return redirect('facetedhome', resulttype=resulttype)
		return redirect(newurl_stub)
	
	# ORDERINGS: if not provided it defaults to the standard one (as defined in the model)
	try:
		from djfacet.orderings import *
		real_ordering = determine_ordering(resulttype, ordering)
	except:
		real_ordering = 'default'
	# UPDATE_RESULTS (note: new_queryargs has the new format=list of fvalues!)
	new_items, new_queryargs, new_activeIDs = update_results(resulttype, real_ordering, query_filtersUrl, query_filtersBuffer, activeIDs)

	# PAGINATION  
	 # ===>> rem that: 'items' is not a collection, so they are accessed through 'objects_list' method in the template
	paginator = Paginator(new_items, DJ_MAX_RESULTS_PER_PAGE) # ie show 50 items per page
	try:
		items = paginator.page(page)
	except (EmptyPage, InvalidPage):  # If page request is out of range, deliver last page of results.
		items = paginator.page(paginator.num_pages)
	# add other useful paginator data to the object
	items.extrastuff = paginator_helper(page, paginator.num_pages)
	items.totcount = paginator.count

	# (RE)CREATE THE FACETS COLUMN: 
	facetgroups_and_facets = get_facetsForTemplate(new_queryargs, new_activeIDs, resulttype)
	
	# HELPER URL STRINGA creation, from the active filters
	newurl_stub = create_queryUrlStub(query_filtersUrl)
	
	# RESET THE SESSION INFO 
	request.session['active_resulttype'] = resulttype	# a string (=uniquename) ... used by updateFacets
	request.session['query_filtersBuffer'] = new_queryargs	# a list of FValues..
	request.session['activeIDs'] = new_activeIDs
	request.session.set_expiry(300) # 5 minutes ..	too much?

	# GET THE RIGHT TEMPLATE FOR RESULTS LIST
	table_template = select_template([ "djfacet/%s/%s_table.html" % (resulttype, resulttype), 
										"djfacet/results_list_table.html"])
	table_template = table_template.name
	
	single_item_template = select_template([ "djfacet/%s/%s_item.html" % (resulttype, resulttype), 
										"djfacet/results_list_item.html"])
	single_item_template = single_item_template.name

	if single_item and single_item_template == "djfacet/results_list_item.html":
		# if the template is the default one, just output all fields names..
		this_model = FM_GLOBAL.get_resulttype_from_name(resulttype)['infospace']
		single_item.fields = dict([(field, getattr(single_item, field, None)) for field in this_model._meta.get_all_field_names() if getattr(single_item, field, None)])
	
	context = {	  
		'user_is_logged_in' : request.user.is_authenticated(),
		'facetgroups_and_facets' :	 facetgroups_and_facets,	
		'items' : items,	
		'totitems' : items.totcount ,			
		# 'count_is_active' : DJFACET_COUNT,
		'count_test_button' : DJFACET_TEST,
		'ajaxflag' : DJFACET_AJAX,
		'twocolumnsflag' : DJ_2COLUMNS_INNERFACET,

		'result_types' : RESULT_TYPES,	
		'result_typeObj' : FM_GLOBAL.get_resulttype_from_name(resulttype),	  
		'ordering' : ordering,

		'query_filtersBuffer' : new_queryargs,
		'newurl_stub' : newurl_stub,
		'table_template' : table_template,
		
		'single_item' : single_item,
		'single_item_template' : single_item_template,
		# 'single_item_fields' : single_item_fields,
		}


	return ('djfacet/facetedbrowser.html', context)









def update_results(resulttype, ordering, query_filtersUrl, query_filtersBuffer, activeIDs):
	""" 
	Method that runs a query via the faceted browser. 
	
	The new approach is that each query contains in the GET (query_filtersUrl) all the 
	necessary information for constructing queryargs. 
	The Buffer is used only to determine the 'action', when possible, and adopt alternative heuristics for faster performance

	RESULTTYPE: string representing what result-types are selected (e.g., 'factoids')
	ORDERING: string used to pass ordering directives (starts with '-' for descending order)

	"""

	FM_GLOBAL = access_fmglobal()
	items = None
	# fsearch_history = request.session.get('fsearch', None)   # NEED IT?	
		
	# remember that query_filtersBuffer is a list of FV objects, so the ID is a number, not a string!
	query_filtersBufferIDs = [str(x.id) for x in query_filtersBuffer if x]
				
	filters_difference = list_difference(query_filtersUrl, query_filtersBufferIDs)			

	if len(query_filtersUrl) == 0:
		action = "all"
	elif len(filters_difference) == 1:		
		if (len(query_filtersUrl) - len(query_filtersBufferIDs)) == 1:
			action="add"
			#make sure the latest element is last! (this is needed by the FM run_query algorithm)
			query_filtersUrl.remove(filters_difference[0])	
			query_filtersUrl.append(filters_difference[0])
		if (len(query_filtersBufferIDs) - len(query_filtersUrl)) == 1:
			action="remove" 
	else:
		# in all the other cases..we can't establish a continuity with the previous query (difference = 2 or more..) ... thus just apply the filters sequentially
		action = "reload"  


	# No need to check for valid Ids here: it's already been done in the 'Home' function
	#  TIP: we must set it to null first, in order to avoid caching!!!! 
	queryargs = [] 
	queryargs = [FM_GLOBAL.get_facetvalue_from_id(fvalue_id) for fvalue_id in query_filtersUrl]
	
	djfacetlog("\n\n.. action = %s\n... query_filtersUrl = %s \n.... query_filtersBuffer_IDs = %s	  \n..... **filters_difference** = %s\n...... queryargs = %s\n"% (action, str(query_filtersUrl),  str(query_filtersBufferIDs), str(filters_difference), str([q.id for q in queryargs])), True)	
	djfacetlog("\n....ordering is %s\n" % str(ordering))
	
	# RUN THE QUERY
	result = FM_GLOBAL.run_query(queryargs, resulttype, activeIDs, ordering, action)
	new_activeIDs = result[0]
	items_list = result[1]
	
	djfacetlog("+++++++++++ FACETVIEW: new_activeIDs: [%d] ... now starting Pagination ....." % len(new_activeIDs))

	return [items_list, queryargs, new_activeIDs]










def update_facet(request):
	""" 
	Used in AJAX views to update the contents of a single facet
	"""
	
	FM_GLOBAL = access_fmglobal()
	
	resulttype = request.session.get( 'active_resulttype', None ) # uniquename, we set it all the time
	query_filtersBuffer = request.session.get('query_filtersBuffer', [])  # a list of fValues objects
	activeIDs = request.session.get('activeIDs', [])
	facet_name = request.GET.get('activefacet', None)
	totitems = request.GET.get('totitems', None)  # the current tot number of results: we pass it back to the template renderer to allow the dynamic generatin of <a> tags..
	active_filters = request.GET.getlist('active_filters')	# values hard-coded in the html page!
	ordering = request.GET.get( 'ordering', 'default')	
	queryargs = []
	
	# print "\n\n++active_filters=	 " + str(active_filters)
	
	
	if not len(active_filters) == len(query_filtersBuffer):
		# means we can't use the session.. the back button has been utilized!
		activeIDs = []
		queryargs = [FM_GLOBAL.get_facetvalue_from_id(fvalue_id) for fvalue_id in active_filters]
		# RESET THE SESSION INFO 
		request.session['query_filtersBuffer'] = queryargs	# a list of FValues..
		request.session['activeIDs'] = activeIDs 
	else:
		queryargs = query_filtersBuffer		
	
	try: 
		totitems = int(totitems)
	except:
		totitems = 0
	facet = FM_GLOBAL.get_facet_from_name(facet_name)
		
	if resulttype and facet:
		# TRY THIS TO DEBUG: WHEN YOU USE THE BACK BUTTON, IN CHROME, THE ACTIVEIDS ARE SET BACK TO 0!
		# WHILE IN SAFARI THIS NEVER HAPPENS...
		# print "\nqueryargs=%s\nactiveIDs=%s\nresulttype=%s\nfacet=%s" % (str(queryargs), str(len(activeIDs)), str(resulttype), str(facet))	
		newvalues = FM_GLOBAL.refresh_facetvalues(queryargs, activeIDs, resulttype, facet)
		# print "\n\n ____	" + str(newvalues)
		
		# HELPER URL STRINGA creation, from the active filters
		query_filtersBufferIDs = [str(x.id) for x in queryargs]
		newurl_stub = create_queryUrlStub(query_filtersBufferIDs)
		# find out what's the right context you need....

		if DJ_2COLUMNS_INNERFACET:
			newvalues = split_list_into_two(newvalues)
			innerfacet_template = 'djfacet/snippet_facet2cols.html'
		else:
			innerfacet_template = 'djfacet/snippet_facet.html'			

		context = Context({ 'facetvalues' : newvalues , 
							'facetgroup':	facet.group,
							'facet' :	facet,
							'newurl_stub' : newurl_stub,
							'result_typeObj' : FM_GLOBAL.get_resulttype_from_name(resulttype),	
							'totitems' : totitems ,
							'ordering' : ordering  
							})
							
		return_str = render_block_to_string(innerfacet_template, 
											'inner_facet_values', 
											context)											
											
	else:
		return_str = "Error: resulttype=[%s] facet=[%s]" % (str(resulttype), str(facet))
											

	return HttpResponse(return_str)

















##################
#  
#  OLD STUFF 
#
##################



# view that collects the explanation strings for a given queryargs set
def explain_results(request):
	# global FM_GLOBAL
	FM_GLOBAL = access_fmglobal()

	# -------	
	# 2010-09-28: removed and made global...>>> fmanager = request.session.get('faceted_manager', False)
	queryargs = request.session.get('queryargs', False) 
	resulttype = request.GET.get( 'resulttype' )
	# -------

	expl_dict = FM_GLOBAL.explain_queryargs(queryargs, resulttype)
			
	context = {	  
		'expl_dict' :	 expl_dict, 
		'result_type' : FM_GLOBAL.get_resulttype_from_name(resulttype)['label'],		
		}
		
	return render_to_response('djfacet/explanation.html', 
								context, 
								context_instance=RequestContext(request))
	











# ajax function used to refresh the history panel..
def refresh_fbhistory(request):
	fsearch_history = request.session.get('fsearch', None)
	context = Context({ 'fsearch_history': fsearch_history }) 
	return_str = render_block_to_string('djfacet/faceted.html', 
										'inner_fsearch_history', 
										context)
	return HttpResponse("%s" % (return_str))



# function that makes sure that request.session['fsearch'] is kept up to date 
#  this dict is recognized in the template as 'fsearch_history' and it keeps a log of most recent 20 searches
# it should be in a context processor, but since the FB uses only ajax for now we've put it here...
def update_search_memory(fsearch_history, facetsGroup, active_facet, facetvalue):
	""" """
	newdict = {'group' : {'label' : facetsGroup.label, 'uniquename' :facetsGroup.uniquename} , 
				'facet' : {'name' : active_facet.name , 'uniquename' : active_facet.uniquename }, 
				'value': {'id' : facetvalue.id, 'displayname' : facetvalue.displayname }
				}

	if fsearch_history:
		flag = False  # routine to check for already existing triples
		temp = [facetsGroup.uniquename, active_facet.uniquename, facetvalue.id]

		for d in fsearch_history:
			if [d['group']['uniquename'], d['facet']['uniquename'], d['value']['id']] == temp:
				flag = True
		if not flag:
			fsearch_history.insert(0, newdict)
		if len(fsearch_history) > 20:
			fsearch_history.pop()
	else:
		fsearch_history = [newdict]

	return fsearch_history












